﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.IO;

namespace MartinCarolan.EventSpy
{
    public class HandlerCompiler
    {
        public IEventListener Listener { get; private set; }

        private CSharpCodeProvider codeProvider = new CSharpCodeProvider();
        private ICodeGenerator codeGenerator;
        private CodeGeneratorOptions codeGenerationOptions = new CodeGeneratorOptions();
        private StringBuilder generatedCode = new StringBuilder();
        private StringWriter writer;

        private CodeTypeDeclaration receiverType;

        public HandlerCompiler(IEventListener listener)
        {
            Listener = listener;

            writer = new StringWriter(generatedCode);
            codeGenerator = codeProvider.CreateGenerator(writer);

            var usingSystem = new CodeSnippetCompileUnit("using System;");
            codeGenerator.GenerateCodeFromCompileUnit(usingSystem, writer, codeGenerationOptions);

            receiverType = new CodeTypeDeclaration();
            receiverType.IsClass = true;
            receiverType.Name = "EventSpyReceiver";
            receiverType.TypeAttributes = TypeAttributes.Public;

            receiverType.Members.Add(new CodeMemberField("MartinCarolan.EventSpy.IEventListener", "_listener"));
            receiverType.Members.Add(new CodeMemberField("System.Object", "_target"));

            var listenerProperty = new CodeMemberProperty();
            listenerProperty.Name = "Listener";
            listenerProperty.HasGet = true;
            listenerProperty.HasSet = true;
            listenerProperty.Attributes = MemberAttributes.Public;
            listenerProperty.Type = new CodeTypeReference("MartinCarolan.EventSpy.IEventListener");

            listenerProperty.GetStatements.Add(new CodeSnippetStatement("return _listener;"));
            listenerProperty.SetStatements.Add(new CodeSnippetStatement("_listener = value;"));

            receiverType.Members.Add(listenerProperty);

            var targetProperty = new CodeMemberProperty();
            targetProperty.Name = "Target";
            targetProperty.HasGet = true;
            targetProperty.HasSet = true;
            targetProperty.Attributes = MemberAttributes.Public;
            targetProperty.Type = new CodeTypeReference("System.Object");

            targetProperty.GetStatements.Add(new CodeSnippetStatement("return _target;"));
            targetProperty.SetStatements.Add(new CodeSnippetStatement("_target = value;"));

            receiverType.Members.Add(targetProperty);
        }

        public void AddEvent(EventInfo eventInfo)
        {
            var arguments = eventInfo.EventHandlerType.GetMethod("Invoke").GetParameters();
            var handlerMethod = new CodeMemberMethod();

            handlerMethod.Name = GetHandlerMethodName(eventInfo);
            handlerMethod.ReturnType = new CodeTypeReference(typeof(void));
            handlerMethod.Attributes = MemberAttributes.Public;

            var raiseEventStatement = new StringBuilder();
            raiseEventStatement.Append("Listener.EventRaised(Target, \"");
            raiseEventStatement.Append(eventInfo.Name);
            raiseEventStatement.Append("\", new object[] {");

            foreach (var argument in arguments)
            {
                handlerMethod.Parameters.Add(new CodeParameterDeclarationExpression(argument.ParameterType, argument.Name));
                raiseEventStatement.Append((argument.Position == 0 ? "" : ", ") + argument.Name);
            }

            raiseEventStatement.Append("});");

            handlerMethod.Statements.Add(new CodeSnippetExpression(raiseEventStatement.ToString()));
            receiverType.Members.Add(handlerMethod);
        }

        public string GetHandlerMethodName(EventInfo eventInfo)
        {
            return eventInfo.DeclaringType.Name + eventInfo.Name;
        }

        public object CompileHandler(object target)
        {
            CodeNamespace ns = new CodeNamespace("MartinCarolan.EventSpy");
            ns.Types.Add(receiverType);
            codeGenerator.GenerateCodeFromNamespace(ns, writer, codeGenerationOptions);
            writer.Close();

            var compilerParams = new CompilerParameters();

            compilerParams.GenerateExecutable = false;

            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                if (!asm.IsDynamic)
                {
                    compilerParams.ReferencedAssemblies.Add(asm.Location);
                }
            }

            CompilerResults result = codeProvider.CompileAssemblyFromSource(compilerParams, generatedCode.ToString());

            if (result.Errors.Count > 0)
            {
                throw new CompilationException(result);
            }

            var compiledAssembly = result.CompiledAssembly;
            var instance = compiledAssembly.CreateInstance("MartinCarolan.EventSpy.EventSpyReceiver");

            instance.GetType().GetProperty("Listener").SetValue(instance, Listener, null);
            instance.GetType().GetProperty("Target").SetValue(instance, target, null);

            return instance;
        }
    }
}
